Cryptography
XOR
Source code
#!/opt/pwn.college/python
import random
import sys
key = random.randrange(1, 256)
plain_secret = random.randrange(0, 256)
cipher_secret = plain_secret ^ key
print(f"The key: {key}")
print(f"Encrypted secret: {cipher_secret}")
if int(input("Decrypted secret? ")) == plain_secret:
print("CORRECT! Your flag:")
print(open("/flag").read())
else:
print("INCORRECT!")
sys.exit(1)
hacker@cryptography~xor:/$ /challenge/run
The key: 17
Encrypted secret: 47
Decrypted secret?
In [1]: key = 17
...: encrypted_secret = 47
...: decrypted_secret = key ^ encrypted_secret
...: print(f"Decrypted secret: {decrypted_secret}")
Decrypted secret: 62
Now we can provide this as the answer.
hacker@cryptography~xor:/$ /challenge/run
The key: 17
Encrypted secret: 47
Decrypted secret? 62
CORRECT! Your flag:
pwn.college{I0fIIbzFrXKLaqwpVcvpw0ySDFR.ddjM3kDL4ITM0EzW}
Let's automate the process so that it works for any input.
#!/usr/bin/env python3
import subprocess
import re
# Run the challenge binary and capture the output
proc = subprocess.Popen(["/challenge/run"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
# Read the key and cipher from output
output = proc.stdout.readline()
key = int(re.search(r'\d+', output).group())
output = proc.stdout.readline()
encrypted_secret = int(re.search(r'\d+', output).group())
# Recover the plain secret using XOR
decrypted_secret = key ^ encrypted_secret
# Send the recovered decrypted_secret as input
proc.stdin.write(f"{decrypted_secret}\n")
proc.stdin.flush()
# Print the remaining output (CORRECT! or INCORRECT! and possibly the flag)
for line in proc.stdout:
print(line, end="")
XORing Hex
Source code
#!/opt/pwn.college/python
import random
import sys
for n in range(10):
print(f"Challenge number {n}...")
key = random.randrange(1, 256)
plain_secret = random.randrange(0, 256)
cipher_secret = plain_secret ^ key
print(f"The key: {key:#04x}")
print(f"Encrypted secret: {cipher_secret:#04x}")
answer = int(input("Decrypted secret? "), 16)
print(f"You entered: {answer:#04x}, decimal {answer}.")
if answer != plain_secret:
print("INCORRECT!")
sys.exit(1)
print("Correct! Moving on.")
print("CORRECT! Your flag:")
print(open("/flag").read())
hacker@cryptography~xoring-hex:/$ /challenge/run
Challenge number 0...
The key: 0x11
Encrypted secret: 0xfd
Decrypted secret?
In [1]: key = 0x11
...: encrypted_secret = 0xfd
...: decrypted_secret = hex(key ^ encrypted_secret)
...: print(f"Decrypted secret: {decrypted_secret}")
Decrypted secret: 0xec
Let's enter this.
hacker@cryptography~xoring-hex:/$ /challenge/run
Challenge number 0...
The key: 0x11
Encrypted secret: 0xfd
Decrypted secret? 0xec
You entered: 0xec, decimal 236.
Correct! Moving on.
Challenge number 1...
The key: 0x9a
Encrypted secret: 0xe5
Decrypted secret?
We can see that loops over and asks the same question. Let's automate the process.
#!/usr/bin/env python3
import subprocess
import re
# Start the challenge process
proc = subprocess.Popen(
["/challenge/run"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
while True:
line = proc.stdout.readline()
if not line:
break
print(line, end="")
if line.startswith("The key:"):
key = int(line.strip().split(":")[1], 16)
encrypted_line = proc.stdout.readline()
print(encrypted_line, end="")
encrypted_secret = int(encrypted_line.strip().split(":")[1], 16)
decrypted_secret = encrypted_secret ^ key
proc.stdin.write(f"{decrypted_secret:#04x}\n")
proc.stdin.flush()
elif "INCORRECT!" in line or "CORRECT! Your flag:" in line:
# Print remaining lines (flag or failure)
for out_line in proc.stdout:
print(out_line, end="")
break
hacker@cryptography~xoring-hex:/$ python ~/script.py
Challenge number 0...
The key: 0xde
Encrypted secret: 0x5d
Decrypted secret? You entered: 0x83, decimal 131.
Correct! Moving on.
Challenge number 1...
The key: 0x2c
Encrypted secret: 0x09
Decrypted secret? You entered: 0x25, decimal 37.
Correct! Moving on.
Challenge number 2...
The key: 0x1f
Encrypted secret: 0x30
Decrypted secret? You entered: 0x2f, decimal 47.
Correct! Moving on.
Challenge number 3...
The key: 0x38
Encrypted secret: 0x0e
Decrypted secret? You entered: 0x36, decimal 54.
Correct! Moving on.
Challenge number 4...
The key: 0x74
Encrypted secret: 0xe6
Decrypted secret? You entered: 0x92, decimal 146.
Correct! Moving on.
Challenge number 5...
The key: 0x08
Encrypted secret: 0xd9
Decrypted secret? You entered: 0xd1, decimal 209.
Correct! Moving on.
Challenge number 6...
The key: 0x2b
Encrypted secret: 0x09
Decrypted secret? You entered: 0x22, decimal 34.
Correct! Moving on.
Challenge number 7...
The key: 0x38
Encrypted secret: 0xa4
Decrypted secret? You entered: 0x9c, decimal 156.
Correct! Moving on.
Challenge number 8...
The key: 0xf1
Encrypted secret: 0x21
Decrypted secret? You entered: 0xd0, decimal 208.
Correct! Moving on.
Challenge number 9...
The key: 0xcf
Encrypted secret: 0x30
Decrypted secret? You entered: 0xff, decimal 255.
Correct! Moving on.
CORRECT! Your flag:
pwn.college{kSOtmx6AacMeAc4SYmd-sZa4sAf.dBzM3kDL4ITM0EzW}
XORing ASCII
Source code
#!/opt/pwn.college/python
import random
import string
import sys
if not sys.stdin.isatty():
print("You must interact with me directly. No scripting this!")
sys.exit(1)
for n in range(1, 10):
print(f"Challenge number {n}...")
pt_chr, ct_chr = random.sample(
string.digits + string.ascii_letters + string.punctuation,
2
)
key = ord(pt_chr) ^ ord(ct_chr)
print(f"- Encrypted Character: {ct_chr}")
print(f"- XOR Key: {key:#04x}")
answer = input("- Decrypted Character? ").strip()
if answer != pt_chr:
print("Incorrect!")
sys.exit(1)
print("Correct! Moving on.")
print("You have mastered XORing ASCII! Your flag:")
print(open("/flag").read())
hacker@cryptography~xoring-ascii:/$ /challenge/run
Challenge number 1...
- Encrypted Character: _
- XOR Key: 0x37
- Decrypted Character?
In [1]: key = 0x37
...: encrypted_character = ord("_")
...: decrypted_character = chr(key ^ encrypted_character)
...: print(f"Decrypted character: {decrypted_character}")
Decrypted character: h
hacker@cryptography~xoring-ascii:/$ /challenge/run
Challenge number 1...
- Encrypted Character: _
- XOR Key: 0x37
- Decrypted Character? h
Correct! Moving on.
Challenge number 2...
- Encrypted Character: J
- XOR Key: 0x36
- Decrypted Character?
The challenge tries to prevent automation by requiring that the script be run in a real terminal (TTY) — not as a subprocess with piped input/output (like from subprocess.Popen()
).
However, we can use pexpect
which emulates a terminal (PTY), so isatty()
returns True
.
import pexpect
p = pexpect.spawn("/challenge/run", encoding="utf-8")
try:
while True:
p.expect("Encrypted Character: (.)")
encrypted_char = p.match.group(1)
print(f"- Encrypted Char: {encrypted_char}")
p.expect("XOR Key: (0x[0-9a-fA-F]+)")
key = int(p.match.group(1), 16)
print(f"- Key: {key:#04x}")
plain = chr(ord(encrypted_char) ^ key)
p.expect("Decrypted Character\\? ")
p.sendline(plain)
i = p.expect(["Correct! Moving on.", "You have mastered XORing ASCII! Your flag:", "INCORRECT!"])
print(p.after)
if i == 1:
print(p.readline()) # Print the flag
break
except pexpect.EOF:
remaining = p.before.strip()
if remaining:
print(remaining)
hacker@cryptography~xoring-ascii:/$ python ~/script.py
- Encrypted Char: x
- Key: 0x23
Correct! Moving on.
- Encrypted Char: C
- Key: 0x3e
Correct! Moving on.
- Encrypted Char: k
- Key: 0x47
Correct! Moving on.
- Encrypted Char: G
- Key: 0x2d
Correct! Moving on.
- Encrypted Char: *
- Key: 0x10
Correct! Moving on.
- Encrypted Char: Y
- Key: 0x23
Correct! Moving on.
- Encrypted Char: /
- Key: 0x64
Correct! Moving on.
- Encrypted Char: /
- Key: 0x6d
Correct! Moving on.
- Encrypted Char: p
- Key: 0x44
Correct! Moving on.
You have mastered XORing ASCII! Your flag:
pwn.college{w4z-Se86BfDzuA00U686p_yy3aR.dhjM3kDL4ITM0EzW}
XORing ASCII Strings
Source code
#!/opt/pwn.college/python
import random
import string
import sys
from Crypto.Util.strxor import strxor
valid_keys = "!#$%&()"
valid_chars = ''.join(
c for c in string.ascii_letters
if all(chr(ord(k)^ord(c)) in string.ascii_letters for k in valid_keys)
)
print(valid_keys, valid_chars)
for n in range(1, 10):
print(f"Challenge number {n}...")
key_str = ''.join(random.sample(valid_keys*10, 10))
pt_str = ''.join(random.sample(valid_chars*10, 10))
ct_str = strxor(pt_str.encode(), key_str.encode()).decode()
print(f"- Encrypted String: {ct_str}")
print(f"- XOR Key String: {key_str}")
answer = input("- Decrypted String? ").strip()
if answer != pt_str:
print("Incorrect!")
sys.exit(1)
print("Correct! Moving on.")
print("You have mastered XORing ASCII! Your flag:")
print(open("/flag").read())
hacker@cryptography~xoring-ascii-strings:/$ /challenge/run
!#$%&() bgjklmnopqBGJKLMNOPQ
Challenge number 1...
- Encrypted String: iEndTdkaTB
- XOR Key String: $)#(%#&&$)
- Decrypted String?
In [1]: from Crypto.Util.strxor import strxor
...: encrypted_string = b"iEndTdkaTB"
...: key = b"$)#(%#&&$)"
...: decrypted_string = strxor(encrypted_string, key)
...: print(f"Decrypted string: {decrypted_string}")
Decrypted string: b'MlMLqGMGpk'
hacker@cryptography~xoring-ascii-strings:/$ /challenge/run
!#$%&() bgjklmnopqBGJKLMNOPQ
Challenge number 1...
- Encrypted String: iEndTdkaTB
- XOR Key String: $)#(%#&&$)
- Decrypted String? MlMLqGMGpk
Correct! Moving on.
Challenge number 2...
- Encrypted String: jjPGSLDkfF
- XOR Key String: !$!%##)!))
- Decrypted String?
Let's automate.
#!/usr/bin/env python3
import subprocess
from Crypto.Util.strxor import strxor
# Start the challenge process
proc = subprocess.Popen(
["/challenge/run"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1
)
while True:
line = proc.stdout.readline()
if not line:
break
print(line, end="")
if line.startswith("- Encrypted String:"):
encrypted_str = line.strip().split(": ", 1)[1]
key_line = proc.stdout.readline()
print(key_line, end="")
key_str = key_line.strip().split(": ", 1)[1]
# Read until the exact string prompt, not a full line
prompt = ""
while not prompt.endswith("- Decrypted String? "):
char = proc.stdout.read(1)
if not char:
break
prompt += char
print(char, end="")
# XOR and decode
decrypted = strxor(
encrypted_str.encode("latin1"),
key_str.encode("latin1")
).decode("latin1")
proc.stdin.write(decrypted + "\n")
proc.stdin.flush()
elif "Incorrect!" in line or "Your flag:" in line:
for out_line in proc.stdout:
print(out_line, end="")
break
hacker@cryptography~xoring-ascii-strings:/$ python ~/script.py
!#$%&() bgjklmnopqBGJKLMNOPQ
Challenge number 1...
- Encrypted String: WUNKDbHUHK
- XOR Key String: &%!&#)%%#$
- Decrypted String? Correct! Moving on.
Challenge number 2...
- Encrypted String: GTOLCfubob
- XOR Key String: (%!#)$$%!(
- Decrypted String? Correct! Moving on.
Challenge number 3...
- Encrypted String: OBovGMfWfi
- XOR Key String: !($&%!!&)$
- Decrypted String? Correct! Moving on.
Challenge number 4...
- Encrypted String: JhHMFiCkjM
- XOR Key String: $#$#(#)%(&
- Decrypted String? Correct! Moving on.
Challenge number 5...
- Encrypted String: ICSnhGkySK
- XOR Key String: #!#%#)))#$
- Decrypted String? Correct! Moving on.
Challenge number 6...
- Encrypted String: IDJFcIjdCk
- XOR Key String: $#%!!&!&(&
- Decrypted String? Correct! Moving on.
Challenge number 7...
- Encrypted String: KmlxeHnfMe
- XOR Key String: &#!()&!)#(
- Decrypted String? Correct! Moving on.
Challenge number 8...
- Encrypted String: iBftYGYIfF
- XOR Key String: %%)%(()$((
- Decrypted String? Correct! Moving on.
Challenge number 9...
- Encrypted String: joJfCjYJSi
- XOR Key String: %#!!)%(%#%
- Decrypted String? Correct! Moving on.
You have mastered XORing ASCII! Your flag:
pwn.college{Mu8QkjC0REoDOGVQrHicFcg9hJ7.dljM3kDL4ITM0EzW}
One-time Pad
Source code
#!/opt/pwn.college/python
from Crypto.Random import get_random_bytes
from Crypto.Util.strxor import strxor
flag = open("/flag", "rb").read()
key = get_random_bytes(len(flag))
ciphertext = strxor(flag, key)
print(f"One-Time Pad Key (hex): {key.hex()}")
print(f"Flag Ciphertext (hex): {ciphertext.hex()}")
hacker@cryptography~one-time-pad:/$ /challenge/run
One-Time Pad Key (hex): 7d29459b4f15aa95736ac4a5a348a95ed9c7df9e745f2147572e59385df9918e15b73ffc160d0b27ba0b460c821752ff15f26b39e15bb6a9da3d
Flag Ciphertext (hex): 0d5e2bb52c7ac6f9160da1dee63b9d36839fe9d41c0d44773446126c18b0d9e445ce7eba5e646509de593c42f85a16b321bb3f74d11eccfea737
This time, we the flag is encrypted using One-time pad. In order to get the original, we have to get XOR the bits of the plaintext with the bits of the key one by one.
key_hex = "7d29459b4f15aa95736ac4a5a348a95ed9c7df9e745f2147572e59385df9918e15b73ffc160d0b27ba0b460c821752ff15f26b39e15bb6a9da3d"
cipher_hex = "0d5e2bb52c7ac6f9160da1dee63b9d36839fe9d41c0d44773446126c18b0d9e445ce7eba5e646509de593c42f85a16b321bb3f74d11eccfea737"
key = bytes.fromhex(key_hex)
cipher = bytes.fromhex(cipher_hex)
plaintext = bytes([c ^ k for c, k in zip(cipher, key)])
print("Decrypted flag:", plaintext.decode())
hacker@cryptography~one-time-pad:/$ python ~/script.py
Decrypted flag: pwn.college{Es4hZX6JhRe0chKTEIHjPyAFHin.dRzNzMDL4ITM0EzW}
One-time Pad tampering
Source code
#!/opt/pwn.college/python
from Crypto.Util.strxor import strxor
key = open("/challenge/.key", "rb").read()
ciphertext = strxor(b"sleep", key[:5])
#!/opt/pwn.college/python
from Crypto.Util.strxor import strxor
import time
import sys
key = open("/challenge/.key", "rb").read()
while line := sys.stdin.readline():
if not line.startswith("TASK: "):
continue
data = bytes.fromhex(line.split()[1])
cipher_len = min(len(data), len(key))
plaintext = strxor(data[:cipher_len], key[:cipher_len])
print(f"Hex of plaintext: {plaintext.hex()}")
print(f"Received command: {plaintext}")
if plaintext == b"sleep":
print("Sleeping!")
time.sleep(1)
elif plaintext == b"flag!":
print("Victory! Your flag:")
print(open("/flag").read())
else:
print("Unknown command!")
The /challenge/dispatcher
script gives us the cipher task in hex.
hacker@cryptography~one-time-pad-tampering:/$ /challenge/dispatcher
TASK: 6286bb6ab5
If we provide this as input to /challenge/worker
, it will first convert the hex string into bytes, then XOR it with the key to recover the plaintext, print the hex of the plaintext, and finally execute the resulting command.
hacker@cryptography~one-time-pad-tampering:/$ /challenge/worker
TASK: 6286bb6ab5
Hex of plaintext: 736c656570
Received command: b'sleep'
Sleeping!
Let's script the solution.
from Crypto.Util.strxor import strxor
# Known ciphertext
sleep_cipher_hex = "6286bb6ab5"
sleep_cipher = bytes.fromhex(sleep_cipher_hex)
# Known plaintext
known_plaintext_hex = "736c656570"
known_plaintext = bytes.fromhex(known_plaintext_hex)
# Or just use the following:
# known_plaintext = b"sleep"
# XOR to get key fragment
key_fragment = strxor(sleep_cipher, known_plaintext)
# Encrypt "flag!" with recovered key
command = b"flag!"
encrypted = strxor(command, key_fragment[:len(command)])
# Print encrypted hex
print(encrypted.hex())
hacker@cryptography~one-time-pad-tampering:/$ python ~/script.py
7786bf68e4
hacker@cryptography~one-time-pad-tampering:/$ /challenge/worker
TASK: 7786bf68e4
Hex of plaintext: 666c616721
Received command: b'flag!'
Victory! Your flag:
pwn.college{sHPpXIo63DeFuiDt1Qm6aBHZXv_.QXzcTO2EDL4ITM0EzW}
Many-time Pad
Source
#!/opt/pwn.college/python
from Crypto.Random import get_random_bytes
from Crypto.Util.strxor import strxor
flag = open("/flag", "rb").read()
key = get_random_bytes(256)
ciphertext = strxor(flag, key[:len(flag)])
print(f"Flag Ciphertext (hex): {ciphertext.hex()}")
while True:
plaintext = bytes.fromhex(input("Plaintext (hex): "))
ciphertext = strxor(plaintext, key[:len(plaintext)])
print(f"Ciphertext (hex): {ciphertext.hex()}")
hacker@cryptography~many-time-pad:/$ /challenge/run
Flag Ciphertext (hex): a7cd55d0b293a2dd60c1d2ca382f0b3ccf14b9a500cc14a4f11faa2569f598a9da94a803cd064726b504e1f8e5752e46610d6ba2a15865968dac
Plaintext (hex):
The challenge XORs the raw bytes of flag with the raw bytes of key, and then prints out the cipher flag in hex. It then asks for plaintext in hex format, which it converts into raw bytes.
We know that XOR is commutative.
flag plaintext = flag ciphertext ^ key
key = flag plaintext ^ flag ciphertext
So, if we just give back the Flag Ciphertext (hex)
, we would het back Key (hex)
, which we simply have to convert into bytes.
hacker@cryptography~many-time-pad:/$ /challenge/run
Flag Ciphertext (hex): a7cd55d0b293a2dd60c1d2ca382f0b3ccf14b9a500cc14a4f11faa2569f598a9da94a803cd064726b504e1f8e5752e46610d6ba2a15865968dac
Plaintext (hex): a7cd55d0b293a2dd60c1d2ca382f0b3ccf14b9a500cc14a4f11faa2569f598a9da94a803cd064726b504e1f8e5752e46610d6ba2a15865968dac
Ciphertext (hex): 70776e2e636f6c6c6567657b4568673834787048625133486451307a6d6853705849763272716a2e64567a4e7a4d444c3449544d30457a577d0a
Plaintext (hex):
In [1]: flag = bytes.fromhex("70776e2e636f6c6c6567657b4568673834787048625133486451307a6d6853705849763272716a2e64567a4e7a4d444c3449544d30457a577d0a").decode()
...: print(flag)
pwn.college{Ehg84xpHbQ3HdQ0zmhSpXIv2rqj.dVzNzMDL4ITM0EzW}
AES
Source code
#!/opt/pwn.college/python
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Random import get_random_bytes
flag = open("/flag", "rb").read()
key = get_random_bytes(16)
cipher = AES.new(key=key, mode=AES.MODE_ECB)
ciphertext = cipher.encrypt(pad(flag, cipher.block_size))
print(f"AES Key (hex): {key.hex()}")
print(f"Flag Ciphertext (hex): {ciphertext.hex()}")
hacker@cryptography~aes:/$ /challenge/run
AES Key (hex): 5ad958b13b3f977f623de7e7a4f57345
Flag Ciphertext (hex): 5792b94f7f852e4f87caaef91abbfdb724c3e6cd6010bffeefa06c33ae48e3975538da1de3e1f269c82eaaccad6456b24aef08329cf3e41b0bfaae5c82eb6ad2
We can simply decode the flag cipher using the Crypto
library.
#!/opt/pwn.college/python
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
key_hex = "5ad958b13b3f977f623de7e7a4f57345"
key = bytes.fromhex(key_hex)
flag_cipher_hex = "5792b94f7f852e4f87caaef91abbfdb724c3e6cd6010bffeefa06c33ae48e3975538da1de3e1f269c82eaaccad6456b24aef08329cf3e41b0bfaae5c82eb6ad2"
flag_cipher = bytes.fromhex(flag_cipher_hex)
cipher = AES.new(key=key, mode=AES.MODE_ECB)
flag_plain = unpad(cipher.decrypt(flag_cipher), cipher.block_size)
print(flag_plain.decode())
hacker@cryptography~aes:/$ python ~/script.py
pwn.college{4mDLEMrwZcIDc7LzmbiYfdSlcYV.dZzNzMDL4ITM0EzW}
AES-ECB-CPA
Source code
#!/opt/pwn.college/python
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Random import get_random_bytes
flag = open("/flag", "rb").read()
key = get_random_bytes(16)
cipher = AES.new(key=key, mode=AES.MODE_ECB)
while True:
print("Choose an action?")
print("1. Encrypt chosen plaintext.")
print("2. Encrypt part of the flag.")
if (choice := int(input("Choice? "))) == 1:
pt = input("Data? ").strip().encode()
elif choice == 2:
index = int(input("Index? "))
length = int(input("Length? "))
pt = flag[index:index+length]
else:
break
ct = cipher.encrypt(pad(pt, cipher.block_size))
print(f"Result: {ct.hex()}")
We have to build a lookup table of 1 byte encrypted outputs for every possible printable flag character (e.g., a-zA-Z0-9_{}-
, etc.).
Then, For each flag index i
:
- Use Option 2 to encrypt 1 byte:
flag[i]
. - Compare with our table.
- Recover the flag character at that position.
#!/usr/bin/env python3
from pwn import *
import string
context.log_level = 'error'
p = process("/challenge/run")
def send_choice(choice):
p.sendlineafter("Choice?", str(choice))
def encrypt_custom(pt: bytes):
send_choice(1)
p.sendlineafter("Data?", pt.decode()) # safe since we're only using printable
p.recvuntil("Result: ")
return bytes.fromhex(p.recvlineS().strip())
def encrypt_flag_byte(index):
send_choice(2)
p.sendlineafter("Index?", str(index))
p.sendlineafter("Length?", "1")
p.recvuntil("Result: ")
return bytes.fromhex(p.recvlineS().strip())
# Only safe printable characters
charset = string.printable.strip()
# Build lookup
print("[*] Building lookup table of printable characters...")
lookup = {}
for ch in charset:
ct = encrypt_custom(ch.encode())
lookup[ct] = ch
# Add '}' manually (may be missed by .strip())
if '}' not in charset:
ct = encrypt_custom(b'}')
lookup[ct] = '}'
# Recover flag
flag = "pwn.college{"
i = len(flag)
while True:
ct = encrypt_flag_byte(i)
ch = lookup.get(ct)
if not ch:
flag += '?'
print(f"[!] Unknown character at index {i}")
break
flag += ch
print(f"[+] {flag}")
if ch == '}':
break
i += 1
print(f"\n[*] Final flag: {flag}")
hacker@cryptography~aes-ecb-cpa:/$ python ~/script.py
[*] Building lookup table of printable characters...
/home/hacker/script.py:9: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.sendlineafter("Choice?", str(choice))
/nix/store/mjsfqdhpiqz69xczkhcycqmzs4x0xgk6-python3-3.12.8-env/lib/python3.12/site-packages/pwnlib/tubes/tube.py:841: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
res = self.recvuntil(delim, timeout=timeout)
/home/hacker/script.py:13: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.sendlineafter("Data?", pt.decode()) # safe since we're only using printable
/home/hacker/script.py:14: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil("Result: ")
/home/hacker/script.py:19: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.sendlineafter("Index?", str(index))
/home/hacker/script.py:20: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.sendlineafter("Length?", "1")
/home/hacker/script.py:21: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil("Result: ")
[+] pwn.college{0
[+] pwn.college{0G
[+] pwn.college{0GU
[+] pwn.college{0GUW
[+] pwn.college{0GUWK
[+] pwn.college{0GUWKp
[+] pwn.college{0GUWKpO
[+] pwn.college{0GUWKpOu
[+] pwn.college{0GUWKpOuO
[+] pwn.college{0GUWKpOuOD
[+] pwn.college{0GUWKpOuOD7
[+] pwn.college{0GUWKpOuOD7x
[+] pwn.college{0GUWKpOuOD7x0
[+] pwn.college{0GUWKpOuOD7x09
[+] pwn.college{0GUWKpOuOD7x095
[+] pwn.college{0GUWKpOuOD7x095Y
[+] pwn.college{0GUWKpOuOD7x095YQ
[+] pwn.college{0GUWKpOuOD7x095YQk
[+] pwn.college{0GUWKpOuOD7x095YQkH
[+] pwn.college{0GUWKpOuOD7x095YQkHY
[+] pwn.college{0GUWKpOuOD7x095YQkHYH
[+] pwn.college{0GUWKpOuOD7x095YQkHYHN
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNX
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNXF
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNXF1
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNXF10
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNXF105
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNXF105.
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNXF105.d
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNXF105.dF
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNXF105.dFz
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNXF105.dFzM
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNXF105.dFzM3
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNXF105.dFzM3k
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNXF105.dFzM3kD
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNXF105.dFzM3kDL
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNXF105.dFzM3kDL4
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNXF105.dFzM3kDL4I
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNXF105.dFzM3kDL4IT
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNXF105.dFzM3kDL4ITM
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNXF105.dFzM3kDL4ITM0
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNXF105.dFzM3kDL4ITM0E
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNXF105.dFzM3kDL4ITM0Ez
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNXF105.dFzM3kDL4ITM0EzW
[+] pwn.college{0GUWKpOuOD7x095YQkHYHNXF105.dFzM3kDL4ITM0EzW}
[*] Final flag: pwn.college{0GUWKpOuOD7x095YQkHYHNXF105.dFzM3kDL4ITM0EzW}
AES-ECB-CPA-HTTP
Source code
#!/opt/pwn.college/python
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Random import get_random_bytes
import tempfile
import sqlite3
import random
import flask
import os
app = flask.Flask(__name__)
class TemporaryDB:
def __init__(self):
self.db_file = tempfile.NamedTemporaryFile("x", suffix=".db")
def execute(self, sql, parameters=()):
connection = sqlite3.connect(self.db_file.name)
connection.row_factory = sqlite3.Row
cursor = connection.cursor()
result = cursor.execute(sql, parameters)
connection.commit()
return result
key = get_random_bytes(16)
cipher = AES.new(key=key, mode=AES.MODE_ECB)
db = TemporaryDB()
# https://www.sqlite.org/lang_createtable.html
db.execute("""CREATE TABLE secrets AS SELECT ? AS flag""", [open("/flag").read()])
@app.route("/", methods=["GET"])
def challenge_get():
query = flask.request.args.get("query") or "'A'"
try:
sql = f'SELECT {query} FROM secrets'
print(f"DEBUG: {sql=}")
pt = db.execute(sql).fetchone()[0]
except sqlite3.Error as e:
flask.abort(500, f"Query: {query}\nError: {e}")
except TypeError:
# no records found
pt = "A"
ct = cipher.encrypt(pad(pt.encode(), cipher.block_size))
return f"""
<html><body>Welcome to pwn.secret!
<form>SELECT <input type=text name=query value='{query}'> FROM secrets<br><input type=submit value=Submit></form>
<hr>
<b>Query:</b> <pre>{sql}</pre><br>
<b>Results:</b><pre>{ct.hex()}</pre>
</body></html>
"""
app.secret_key = os.urandom(8)
app.config['SERVER_NAME'] = f"challenge.localhost:80"
app.run("challenge.localhost", 80)
#!/usr/bin/env python3
import requests
import string
url = "http://challenge.localhost/"
# Safer charset: avoids single quote, backslash, etc.
charset = string.printable.strip()
print("[*] Building lookup table...")
lookup = {}
for ch in charset:
try:
# Avoid breaking the SQL string with unsafe chars
query_param = f"'{ch}'"
r = requests.get(url, params={"query": query_param})
parts = r.text.split("<pre>")
if len(parts) < 3:
print(f"[!] Skipping char: {repr(ch)} — malformed response")
continue
ct = parts[2].split("</pre>")[0].strip()
lookup[ct] = ch
except Exception as e:
print(f"[!] Error with char {repr(ch)}: {e}")
print(f"[+] Lookup table built with {len(lookup)} entries")
# Step 2: Extract flag
flag = "pwn.college{"
i = len(flag) + 1
while True:
try:
r = requests.get(url, params={"query": f"substr(flag,{i},1)"})
parts = r.text.split("<pre>")
if len(parts) < 3:
print(f"[!] Failed to extract flag[{i}] — malformed response")
break
ct = parts[2].split("</pre>")[0].strip()
ch = lookup.get(ct)
if not ch:
print(f"[!] Unknown character at position {i}, ciphertext: {ct}")
break
flag += ch
print(f"[+] {flag}")
if ch == "}":
break
i += 1
except Exception as e:
print(f"[!] Error on index {i}: {e}")
break
print(f"\n[*] Final flag: {flag}")
hacker@cryptography~aes-ecb-cpa-http:/$ python ~/script.py
[*] Building lookup table...
[+] Lookup table built with 87 entries
[+] pwn.college{c
[+] pwn.college{cM
[+] pwn.college{cMH
[+] pwn.college{cMHT
[+] pwn.college{cMHTy
[+] pwn.college{cMHTyv
[+] pwn.college{cMHTyvr
[+] pwn.college{cMHTyvrP
[+] pwn.college{cMHTyvrP_
[+] pwn.college{cMHTyvrP_6
[+] pwn.college{cMHTyvrP_6V
[+] pwn.college{cMHTyvrP_6Vg
[+] pwn.college{cMHTyvrP_6VgT
[+] pwn.college{cMHTyvrP_6VgTc
[+] pwn.college{cMHTyvrP_6VgTc5
[+] pwn.college{cMHTyvrP_6VgTc5N
[+] pwn.college{cMHTyvrP_6VgTc5NU
[+] pwn.college{cMHTyvrP_6VgTc5NUM
[+] pwn.college{cMHTyvrP_6VgTc5NUMc
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3G
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gd
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdn
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdns
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8x
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8x.
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8x.Q
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8x.QX
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8x.QX3
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8x.QX3E
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8x.QX3Ez
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8x.QX3EzM
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8x.QX3EzM2
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8x.QX3EzM2E
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8x.QX3EzM2ED
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8x.QX3EzM2EDL
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8x.QX3EzM2EDL4
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8x.QX3EzM2EDL4I
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8x.QX3EzM2EDL4IT
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8x.QX3EzM2EDL4ITM
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8x.QX3EzM2EDL4ITM0
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8x.QX3EzM2EDL4ITM0E
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8x.QX3EzM2EDL4ITM0Ez
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8x.QX3EzM2EDL4ITM0EzW
[+] pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8x.QX3EzM2EDL4ITM0EzW}
[*] Final flag: pwn.college{cMHTyvrP_6VgTc5NUMc3Gdnsj8x.QX3EzM2EDL4ITM0EzW}
AES-ECB-CPA-HTTP (base64)
Source code
#!/opt/pwn.college/python
from base64 import b64encode, b64decode
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Random import get_random_bytes
import tempfile
import sqlite3
import random
import flask
import os
app = flask.Flask(__name__)
class TemporaryDB:
def __init__(self):
self.db_file = tempfile.NamedTemporaryFile("x", suffix=".db")
def execute(self, sql, parameters=()):
connection = sqlite3.connect(self.db_file.name)
connection.row_factory = sqlite3.Row
cursor = connection.cursor()
result = cursor.execute(sql, parameters)
connection.commit()
return result
key = get_random_bytes(16)
cipher = AES.new(key=key, mode=AES.MODE_ECB)
db = TemporaryDB()
# https://www.sqlite.org/lang_createtable.html
db.execute("""CREATE TABLE secrets AS SELECT ? AS flag""", [open("/flag").read()])
@app.route("/", methods=["GET"])
def challenge_get():
query = flask.request.args.get("query") or "'A'"
try:
sql = f'SELECT {query} FROM secrets'
print(f"DEBUG: {sql=}")
pt = db.execute(sql).fetchone()[0]
except sqlite3.Error as e:
flask.abort(500, f"Query: {query}\nError: {e}")
except TypeError:
# no records found
pt = "A"
ct = cipher.encrypt(pad(pt.encode(), cipher.block_size))
return f"""
<html><body>Welcome to pwn.secret!
<form>SELECT <input type=text name=query value='{query}'> FROM secrets<br><input type=submit value=Submit></form>
<hr>
<b>Query:</b> <pre>{sql}</pre><br>
<b>Results:</b><pre>{b64encode(ct).decode()}</pre>
</body></html>
"""
app.secret_key = os.urandom(8)
app.config['SERVER_NAME'] = f"challenge.localhost:80"
app.run("challenge.localhost", 80)
This time the output is given to us after Base64 encoding. We just need to modify the last level's script slightly.
#!/usr/bin/env python3
import requests
import string
import base64
url = "http://challenge.localhost/"
# Safer charset: avoids single quote, backslash, etc.
safe_charset = string.printable.strip()
print("[*] Building lookup table...")
lookup = {}
for ch in safe_charset:
try:
query_param = f"'{ch}'"
r = requests.get(url, params={"query": query_param})
parts = r.text.split("<pre>")
if len(parts) < 3:
print(f"[!] Skipping char: {repr(ch)} — malformed response")
continue
ct_b64 = parts[2].split("</pre>")[0].strip()
ct = base64.b64decode(ct_b64)
lookup[ct] = ch
except Exception as e:
print(f"[!] Error with char {repr(ch)}: {e}")
print(f"[+] Lookup table built with {len(lookup)} entries")
# Step 2: Extract flag
flag = "pwn.college{"
i = len(flag) + 1
while True:
try:
r = requests.get(url, params={"query": f"substr(flag,{i},1)"})
parts = r.text.split("<pre>")
if len(parts) < 3:
print(f"[!] Failed to extract flag[{i}] — malformed response")
break
ct_b64 = parts[2].split("</pre>")[0].strip()
ct = base64.b64decode(ct_b64)
ch = lookup.get(ct)
if not ch:
print(f"[!] Unknown character at position {i}, ciphertext: {ct_b64}")
break
flag += ch
print(f"[+] {flag}")
if ch == "}":
break
i += 1
except Exception as e:
print(f"[!] Error on index {i}: {e}")
break
print(f"\n[*] Final flag: {flag}")
hacker@cryptography~aes-ecb-cpa-http-base64:/$ python ~/script.py
[*] Building lookup table...
[!] Skipping char: "'" — malformed response
[+] Lookup table built with 93 entries
[+] pwn.college{c
[+] pwn.college{c5
[+] pwn.college{c5U
[+] pwn.college{c5U6
[+] pwn.college{c5U68
[+] pwn.college{c5U68P
[+] pwn.college{c5U68PE
[+] pwn.college{c5U68PEU
[+] pwn.college{c5U68PEUt
[+] pwn.college{c5U68PEUtD
[+] pwn.college{c5U68PEUtD5
[+] pwn.college{c5U68PEUtD57
[+] pwn.college{c5U68PEUtD57I
[+] pwn.college{c5U68PEUtD57I9
[+] pwn.college{c5U68PEUtD57I9r
[+] pwn.college{c5U68PEUtD57I9rE
[+] pwn.college{c5U68PEUtD57I9rEW
[+] pwn.college{c5U68PEUtD57I9rEW5
[+] pwn.college{c5U68PEUtD57I9rEW5l
[+] pwn.college{c5U68PEUtD57I9rEW5ly
[+] pwn.college{c5U68PEUtD57I9rEW5lyj
[+] pwn.college{c5U68PEUtD57I9rEW5lyjw
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk9
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk9B
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk9B_
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk9B_8
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk9B_8.
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk9B_8.d
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk9B_8.dJ
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk9B_8.dJz
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk9B_8.dJzM
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk9B_8.dJzM3
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk9B_8.dJzM3k
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk9B_8.dJzM3kD
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk9B_8.dJzM3kDL
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk9B_8.dJzM3kDL4
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk9B_8.dJzM3kDL4I
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk9B_8.dJzM3kDL4IT
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk9B_8.dJzM3kDL4ITM
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk9B_8.dJzM3kDL4ITM0
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk9B_8.dJzM3kDL4ITM0E
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk9B_8.dJzM3kDL4ITM0Ez
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk9B_8.dJzM3kDL4ITM0EzW
[+] pwn.college{c5U68PEUtD57I9rEW5lyjwk9B_8.dJzM3kDL4ITM0EzW}
[*] Final flag: pwn.college{c5U68PEUtD57I9rEW5lyjwk9B_8.dJzM3kDL4ITM0EzW}